# @Author : wangyuchen
# @Time : 2021-05-27 09:44

from tqdm import tqdm
import network
import utils
import os
import random
import argparse
import numpy as np

from torch.utils import data
from datasets import VOCSegmentation, Cityscapes
from self import TestDataset
from utils import ext_transforms as et
from metrics import StreamSegMetrics

import torch
import torch.nn as nn
from utils.visualizer import Visualizer

from PIL import Image
import matplotlib
import matplotlib.pyplot as plt
from torchvision.models.segmentation import deeplabv3_resnet101
from eval import Dataset_Seg, evaluate
from torch.autograd import Variable
from setting import TIME_NOW
from torchvision import transforms

import ssl

ssl._create_default_https_context = ssl._create_unverified_context


def get_argparser():
    parser = argparse.ArgumentParser()

    # Datset Options
    parser.add_argument("--data_root", type=str, default='./datasets/data/images',
                        help="path to Dataset")
    parser.add_argument("--anno_root", type=str, default='./datasets/data/annotations_trainval',
                        help="path to Annotation")
    parser.add_argument("--dataset", type=str, default='cv',
                        choices=['voc', 'cityscapes', 'cv'], help='Name of dataset')
    parser.add_argument("--num_classes", type=int, default=21,
                        help="num classes (default: None)")
    # Deeplab Options
    parser.add_argument("--model", type=str, default='deeplabv3plus_resnet101',
                        choices=['deeplabv3_resnet50', 'deeplabv3plus_resnet50',
                                 'deeplabv3_resnet101', 'deeplabv3plus_resnet101',
                                 'deeplabv3_mobilenet', 'deeplabv3plus_mobilenet'], help='model name')
    parser.add_argument("--separable_conv", action='store_true', default=False,
                        help="apply separable conv to decoder and aspp")
    parser.add_argument("--output_stride", type=int, default=16, choices=[8, 16])

    # Train Options
    parser.add_argument("--test_only", action='store_true', default=False)
    parser.add_argument("--save_val_results", action='store_true', default=False,
                        help="save segmentation results to \"./results\"")
    parser.add_argument("--total_itrs", type=int, default=3000,
                        help="epoch number (default: 3000)")
    parser.add_argument("--lr", type=float, default=0.0001,
                        help="learning rate (default: 0.0001)")
    parser.add_argument("--lr_policy", type=str, default='poly', choices=['poly', 'step'],
                        help="learning rate scheduler policy")
    parser.add_argument("--step_size", type=int, default=10000)
    parser.add_argument("--crop_val", action='store_true', default=False,
                        help='crop validation (default: False)')
    parser.add_argument("--batch_size", type=int, default=6,
                        help='batch size (default: 6)')
    parser.add_argument("--val_batch_size", type=int, default=1,
                        help='batch size for validation (default: 1)')
    parser.add_argument("--crop_size", type=int, default=513)

    parser.add_argument("--ckpt", default='checkpoints/cv/Thursday_27_May_2021_17h_19m_01s/8-0.9082-0.7799-0.9499.pth',
                        type=str, required=True, help="restore from checkpoint")
    parser.add_argument("--continue_training", action='store_true', default=False)

    parser.add_argument("--loss_type", type=str, default='cross_entropy',
                        choices=['cross_entropy', 'focal_loss'], help="loss type (default: False)")
    parser.add_argument("--gpu_id", type=str, default='0',
                        help="GPU ID")
    parser.add_argument("--weight_decay", type=float, default=1e-4,
                        help='weight decay (default: 1e-4)')
    parser.add_argument("--random_seed", type=int, default=1,
                        help="random seed (default: 1)")
    parser.add_argument("--print_interval", type=int, default=10,
                        help="print interval of loss (default: 10)")
    parser.add_argument("--val_interval", type=int, default=1,
                        help="epoch interval for eval (default: 1)")
    parser.add_argument("--download", action='store_true', default=False,
                        help="download datasets")
    # PASCAL VOC Options
    parser.add_argument("--year", type=str, default='2012',
                        choices=['2012_aug', '2012', '2011', '2009', '2008', '2007'], help='year of VOC')
    # Visdom options
    parser.add_argument("--enable_vis", action='store_true', default=False,
                        help="use visdom for visualization")
    parser.add_argument("--vis_port", type=str, default='60004',
                        help='port for visdom')
    parser.add_argument("--vis_env", type=str, default='main',
                        help='env for visdom')
    parser.add_argument("--vis_num_samples", type=int, default=8,
                        help='number of samples for visualization (default: 8)')
    return parser


def get_dataset(opts):
    test_transform = et.ExtCompose([
        et.ExtResize([opts.crop_size, opts.crop_size]),
        # transforms.RandomHorizontalFlip(),  # 随机水平翻转
        # transforms.RandomRotation(degrees=15),  # 随机旋转
        et.ExtToTensor(),
        et.ExtNormalize(mean=[0.485, 0.456, 0.406],
                        std=[0.229, 0.224, 0.225]),
    ])
    # test_transform = et.ExtCompose([
    #     et.ExtResize(opts.crop_size),
    #     et.ExtToTensor(),
    #     # et.ExtNormalize(mean=[0.485, 0.456, 0.406],
    #     #                 std=[0.229, 0.224, 0.225]),
    # ])
    test_dst = TestDataset(imgs_dir=opts.data_root, transform=test_transform)
    return test_dst


def test(model, loader, device):
    model.eval()
    tmp_path = os.path.join('test-tmp_results', TIME_NOW)
    os.makedirs(tmp_path)
    save_path = os.path.join('test_results', TIME_NOW)
    os.makedirs(save_path)
    denorm = utils.Denormalize(mean=[0.485, 0.456, 0.406],
                               std=[0.229, 0.224, 0.225])

    with torch.no_grad():
        for img_ids, images, Ws, Hs in tqdm(loader):
            images = images.to(device, dtype=torch.float32)
            outputs = model(images)['out']
            preds = outputs.detach().max(dim=1)[1].cpu().numpy()
            for img_id, pred, image, w, h in zip(img_ids, preds, images, Ws, Hs):
                image = image.detach().cpu().numpy()
                image = (denorm(image) * 255).transpose(1, 2, 0).astype(np.uint8)
                pred1 = loader.dataset.decode_target(pred).astype(np.uint8)
                pred2 = pred.astype(np.uint8)

                output_transform = transforms.Compose([
                    transforms.Resize([h, w]),
                ])
                output_transform(Image.fromarray(pred1)).save(tmp_path + '/{id}.png'.format(id=img_id))
                output_transform(Image.fromarray(pred2).convert('P')).save(save_path + '/{id}.png'.format(id=img_id))

                fig = plt.figure()
                plt.imshow(output_transform(Image.fromarray(image)))
                plt.axis('off')
                plt.imshow(output_transform(Image.fromarray(pred.astype(np.uint8))), alpha=0.7)
                ax = plt.gca()
                ax.xaxis.set_major_locator(matplotlib.ticker.NullLocator())
                ax.yaxis.set_major_locator(matplotlib.ticker.NullLocator())
                plt.savefig(tmp_path + '/{id}_overlay.png'.format(id=img_id), bbox_inches='tight', pad_inches=0)
                plt.close()


def main():
    opts = get_argparser().parse_args()
    if opts.dataset.lower() in ['voc', 'cv']:
        opts.num_classes = 21
    elif opts.dataset.lower() == 'cityscapes':
        opts.num_classes = 19

    os.environ['CUDA_VISIBLE_DEVICES'] = opts.gpu_id
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print("Device: %s" % device)

    # Setup random seed
    torch.manual_seed(opts.random_seed)
    np.random.seed(opts.random_seed)
    random.seed(opts.random_seed)

    test_dst = get_dataset(opts)
    test_loader = data.DataLoader(
        test_dst, batch_size=opts.val_batch_size, shuffle=True, num_workers=1)
    print("Test set: %d" % (len(test_loader)))

    model = deeplabv3_resnet101(True, num_classes=21)

    if opts.ckpt is not None and os.path.isfile(opts.ckpt):
        # https://github.com/VainF/DeepLabV3Plus-Pytorch/issues/8#issuecomment-605601402, @PytaichukBohdan
        # checkpoint = torch.load(opts.ckpt, map_location=torch.device('cpu'))
        checkpoint = torch.load(opts.ckpt)
        model.load_state_dict(checkpoint)
        # model.load_state_dict(checkpoint)
        # model = nn.DataParallel(model)
        model.to(device)
        print("Model restored from %s" % opts.ckpt)
        del checkpoint  # free memory
    else:
        print("[!] Pytorch Pretrain")
        # model = nn.DataParallel(model)
        model.to(device)
    test(model, test_loader, device)


if __name__ == '__main__':
    main()
